﻿using System;
using System.Collections.Generic;

namespace QAToolsAddin.Comparer {
    partial class Compare {
        struct Match {
            public int IndexA;
            public int IndexB;
        }

        private bool _ignorecase;

        public bool IgnoreCase {
            get { return _ignorecase; }
            set { _ignorecase = value; }
        }

        public RowSet CombineColumns(ref object[,] dataA, ref object[,] dataB, ref object[,] titlesA, ref object[,] titlesB, int[] colKeysA, int[] colKeysB, int[] colValsA, int[] colValsB) {
            HashRow[] hashTabA = GetRowsKey(dataA, colKeysA);
            HashRow[] hashTabB = GetRowsKey(dataB, colKeysB);
            int nbRowA = hashTabA.Length;
            int nbRowB = hashTabB.Length;
            int[] ptrA = GetSortedHashPtr(ref hashTabA);
            int[] ptrB = GetSortedHashPtr(ref hashTabB);

            int nbRow = 0;
            for (int r1 = 0, r2 = 0, r1ptr, r2ptr, nbMatch; r1 < nbRowA; r1++) {
                r1ptr = ptrA[r1];
                if (hashTabA[r1ptr].IsNotNull) {
                    nbRow++;
                    nbMatch = 0;
                    while (r2 < nbRowB) {
                        r2ptr = ptrB[r2];
                        if (hashTabB[r2ptr].IsNull)
                            r2++;
                        else if (hashTabA[r1ptr] == hashTabB[r2ptr]) {
                            hashTabA[r1ptr].LinkedRow = hashTabB[r2ptr].Row;
                            hashTabB[r2ptr].LinkedRow = hashTabA[r1ptr].Row;
                            r2++;
                            nbMatch = 1;
                        } else if (hashTabA[r1ptr] < hashTabB[r2ptr])
                            break;
                        else
                            r2++;
                    }
                    r2 -= nbMatch;
                }
            }

            foreach (HashRow hashrow in hashTabB)
                if (hashrow.IsNotNull && hashrow.LinkedRow == 0)
                    nbRow++;
            var nbColA = colKeysA.Length + colValsA.Length;
            var nbColB = colValsB.Length;
            var nbCol = nbColA + nbColB;

            var cells = new RowSet(nbRow, nbCol);

            int[] colsA = Array<int>.Join(colKeysA, colValsA);
            Array<int>.Join(colKeysB, colValsB);
            //Copy titles
            for (int c = 0; c < colsA.Length; c++)
                cells.Titles[1, c + 1] = titlesA[1, colsA[c]];
            for (int c = 0, ci = nbColA; c < colValsB.Length; c++)
                cells.Titles[1, ++ci] = titlesB[1, colValsB[c]];
            //Copy data
            int row = 0;
            for (int r = 0, rowA, rowB; r < nbRowA; r++) {
                if (hashTabA[r].IsNotNull) {
                    row++;
                    rowA = hashTabA[r].Row;
                    rowB = hashTabA[r].LinkedRow;
                    for (int c = 0; c < colsA.Length; c++)
                        cells.Data[row, c + 1] = dataA[rowA, colsA[c]];
                    if (rowB != 0) {
                        for (int c = 0, ci = nbColA; c < nbColB; c++)
                            cells.Data[row, ++ci] = dataB[rowB, colValsB[c]];
                    } else {
                        for (int ci = 1; ci <= nbCol; ci++)
                            cells.Diff[row, ci] = true;
                    }
                }
            }

            for (int r = 0, rowB; r < nbRowB; r++) {
                if (hashTabB[r].IsNotNull) {
                    if (hashTabB[r].LinkedRow == 0) {
                        rowB = hashTabB[r].Row;
                        row++;
                        for (int c = 0; c < colKeysB.Length; c++)
                            cells.Data[row, c + 1] = dataB[rowB, colKeysB[c]];
                        for (int c = 0, ci = nbColA; c < nbColB; c++)
                            cells.Data[row, ++ci] = dataB[rowB, colValsB[c]];
                        for (int ci = 1; ci <= nbCol; ci++)
                            cells.Diff[row, ci] = true;
                    }
                }
            }

            return cells;
        }

        public CellSet GetDuplicatedCells(ref object[,] data) {
            HashCell[] hashTab = GetCellsKey(data);
            int[] ptr = GetSortedHashPtr(ref hashTab);
            var cells = new CellSet(data);
            for (int i = 1, iptr1, iptr2; i < ptr.Length; i++) {
                iptr1 = ptr[i - 1];
                iptr2 = ptr[i];
                if (hashTab[iptr2].IsNotNull && hashTab[iptr1] == hashTab[iptr2])
                    cells.Diff[hashTab[iptr2].Row, hashTab[iptr2].Column] = true;
            }
            return cells;
        }

        public CellSet DeleteDuplicatedCells(ref object[,] data) {
            HashCell[] hashTab = GetCellsKey(data);
            int[] ptr = GetSortedHashPtr(ref hashTab);
            int len = hashTab.Length;
            var cells = new CellSet(data.GetLength(0), data.GetLength(1), false);

            for (int i = 1, iptr1, iptr2; i < len; i++) {
                iptr1 = ptr[i - 1];
                iptr2 = ptr[i];
                if (hashTab[iptr2].IsNotNull && hashTab[iptr1] == hashTab[iptr2])
                    hashTab[iptr2].HasMatch = true;
            }
            for (int i = 0, r = 1, c = 1; i < len; i++) {
                if (hashTab[i].IsNotNull && hashTab[i].HasMatch == false) {
                    cells.Data[r, c] = data[hashTab[i].Row, hashTab[i].Column];
                    if (c == cells.ColLen) {
                        r++; c = 1;
                    } else {
                        c++;
                    }
                }
            }
            return cells;
        }

        public CellSet GetDuplicatedRows(ref object[,] data, int[] colKeys, int[] colVal) {
            HashRow[] hashTab = GetRowsKey(data, colKeys);
            int[] ptr = GetSortedHashPtr(ref hashTab);
            int len = hashTab.Length;
            var cells = new CellSet(data);
            HashRow hashRow1 = hashTab[ptr[0]];
            HashRow hashRow2;
            for (int i = 1; i < len; i++) {
                hashRow2 = hashTab[ptr[i]];
                if (hashRow1.IsNotNull && hashRow1 == hashRow2) {
                    foreach (int col in colKeys)
                        cells.Diff[hashRow2.Row, col] = true;
                    foreach (int col in colVal) {
                        if (ObjectsNotEquals(data[hashRow1.Row, col], data[hashRow2.Row, col]) == false)
                            cells.Diff[hashRow2.Row, col] = true;
                    }
                } else
                    hashRow1 = hashRow2;
            }
            return cells;
        }

        public CellSet DeleteDuplicatedRows(ref object[,] data, int[] colKeys, int[] colVal) {
            HashRow[] hashTab = GetRowsKey(data, colKeys);
            int[] ptr = GetSortedHashPtr(ref hashTab);
            int len = hashTab.Length;
            var cells = new CellSet(data.GetLength(0), data.GetLength(1), false);

            for (int i = 1, iptr1 = ptr[0], iptr2 = 0; i < len; i++) {
                iptr2 = ptr[i];
                if (hashTab[iptr1].IsNotNull && hashTab[iptr1] == hashTab[iptr2])
                    hashTab[iptr2].LinkedRow = hashTab[iptr1].Row;
                else
                    iptr1 = iptr2;
            }

            for (int r = 0, row = 0; r < len; r++) {
                if (hashTab[r].IsNotNull) {
                    bool hasDiff = false;
                    if (hashTab[r].LinkedRow != 0) {
                        foreach (int col in colVal) {
                            if (ObjectsNotEquals(data[hashTab[r].Row, col], data[hashTab[r].LinkedRow, col])) {
                                hasDiff = true;
                                break;
                            }
                        }
                    }
                    if (hashTab[r].LinkedRow == 0 || hasDiff) {
                        row++;
                        for (int ci = 1; ci <= cells.ColLen; ci++)
                            cells.Data[row, ci] = data[hashTab[r].Row, ci];
                        if (hasDiff) {
                            foreach (int col in colKeys)
                                cells.Diff[row, col] = true;
                            foreach (int col in colVal) {
                                if (ObjectsNotEquals(data[hashTab[r].Row, col], data[hashTab[r].LinkedRow, col]) == false)
                                    cells.Diff[row, col] = true;
                            }
                        }
                    }
                }
            }
            return cells;
        }

        public CellSet[] CompareOppositeCells(ref object[,] dataA, ref object[,] dataB) {
            var cellsA = new CellSet(dataA);
            var cellsB = new CellSet(dataB);
            int nbMinRow = Math.Min(cellsA.RowLen, cellsB.RowLen);
            int nbMinCol = Math.Min(cellsA.ColLen, cellsB.ColLen);

            for (int ri = 1; ri <= nbMinRow; ri++)
                for (int ci = 1; ci <= nbMinCol; ci++)
                    if (ObjectsNotEquals(dataA[ri, ci], dataB[ri, ci]))
                        cellsA.Diff[ri, ci] = cellsB.Diff[ri, ci] = true;

            for (int ri = nbMinRow + 1; ri <= cellsA.RowLen; ri++)
                for (int ci = 1; ci <= cellsA.ColLen; ci++)
                    cellsA.Diff[ri, ci] = true;
            for (int ri = nbMinRow + 1; ri <= cellsB.RowLen; ri++)
                for (int ci = 1; ci <= cellsB.ColLen; ci++)
                    cellsB.Diff[ri, ci] = true;
            for (int ci = nbMinCol + 1; ci <= cellsA.ColLen; ci++)
                for (int ri = 1; ri <= cellsA.RowLen; ri++)
                    cellsA.Diff[ri, ci] = true;
            for (int ci = nbMinCol + 1; ci <= cellsB.ColLen; ci++)
                for (int ri = 1; ri <= cellsB.RowLen; ri++)
                    cellsB.Diff[ri, ci] = true;

            return new CellSet[] { cellsA, cellsB };
        }

        public CellSet[] CompareListOfCells(ref object[,] dataA, ref object[,] dataB, bool multi) {
            HashCell[] hashTabA = GetCellsKey(dataA);
            HashCell[] hashTabB = GetCellsKey(dataB);
            int[] ptrA = GetSortedHashPtr(ref hashTabA);
            int[] ptrB = GetSortedHashPtr(ref hashTabB);
            int lenA = hashTabA.Length;
            int lenB = hashTabB.Length;

            for (int i = 0, j = 0, nbMatch; i < lenA; i++) {
                int iptr = ptrA[i];
                if (hashTabA[iptr].IsNotNull) {
                    nbMatch = 0;
                    while (j < lenB) {
                        int jptr = ptrB[j];
                        if (hashTabB[jptr].IsNull)
                            j++;
                        else if (hashTabA[iptr] == hashTabB[jptr]) {
                            hashTabA[iptr].HasMatch = hashTabB[jptr].HasMatch = true;
                            j++;
                            nbMatch = 1;
                            if (multi == false)
                                break;
                        } else if (hashTabA[iptr] < hashTabB[jptr])
                            break;
                        else
                            j++;
                    }
                    if (multi)
                        j -= nbMatch;
                }
            }

            var cellsA = new CellSet(dataA);
            var cellsB = new CellSet(dataB);
            for (int i = 0; i < lenA; i++)
                if (hashTabA[i].IsNotNull && !hashTabA[i].HasMatch)
                    cellsA.Diff[hashTabA[i].Row, hashTabA[i].Column] = true;
            for (int j = 0; j < lenB; j++)
                if (hashTabB[j].IsNotNull && !hashTabB[j].HasMatch)
                    cellsB.Diff[hashTabB[j].Row, hashTabB[j].Column] = true;

            return new CellSet[] { cellsA, cellsB };
        }

        public RowSet[] CompareRowsStatic(ref object[,] dataA, ref object[,] dataB, int[] colKeysA, int[] colKeysB, int[] colValA, int[] colValB, bool multi) {
            HashRow[] hashTabA = GetRowsKey(dataA, colKeysA);
            HashRow[] hashTabB = GetRowsKey(dataB, colKeysB);
            int nbRowA = hashTabA.Length;
            int nbRowB = hashTabB.Length;
            int nbValA = colValA.Length;
            int nbValB = colValB.Length;
            int[] ptrA = GetSortedHashPtr(ref hashTabA);
            int[] ptrB = GetSortedHashPtr(ref hashTabB);

            for (int r1 = 0, r2 = 0, nbMatch; r1 < nbRowA; r1++) {
                int r1ptr = ptrA[r1];
                if (hashTabA[r1ptr].IsNotNull) {
                    nbMatch = 0;
                    while (r2 < nbRowB) {
                        int r2ptr = ptrB[r2];
                        if (hashTabB[r2ptr].IsNull)
                            r2++;
                        else if (hashTabA[r1ptr] == hashTabB[r2ptr]) {
                            hashTabA[r1ptr].LinkedRow = hashTabB[r2ptr].Row;
                            hashTabB[r2ptr].LinkedRow = hashTabA[r1ptr].Row;
                            r2++;
                            nbMatch = 1;
                            if (multi == false)
                                break;
                        } else if (hashTabA[r1ptr] < hashTabB[r2ptr])
                            break;
                        else
                            r2++;
                    }
                    if (multi)
                        r2 -= nbMatch;
                }
            }

            var cellsA = new RowSet(dataA);
            var cellsB = new RowSet(dataB);
            int nbColA = cellsA.ColLen;
            int nbColB = cellsB.ColLen;

            for (int r = 0, indexRowA, indexRowB; r < nbRowA; r++) {
                indexRowA = hashTabA[r].Row;
                indexRowB = hashTabA[r].LinkedRow;
                if (indexRowB == 0) {
                    if (hashTabA[r].IsNotNull) {
                        cellsA.Match[indexRowA] = XlRowMatch.NoMatch;
                        for (int ci = 1; ci <= nbColA; ci++)
                            cellsA.Diff[indexRowA, ci] = true;
                    }
                } else {
                    cellsA.Match[indexRowA] = cellsB.Match[indexRowB] = XlRowMatch.FullMatch;
                    for (int i = 0, indexColA, indexColB; i < nbValA; i++) {
                        indexColA = colValA[i];
                        indexColB = colValB[i];
                        if (ObjectsNotEquals(dataA[indexRowA, indexColA], dataB[indexRowB, indexColB])) {
                            cellsA.Diff[indexRowA, indexColA] = cellsB.Diff[indexRowB, indexColB] = true;
                            cellsA.Match[indexRowA] = cellsB.Match[indexRowB] = XlRowMatch.MatchWithDiff;
                        }
                    }
                }
            }

            for (int r = 0, ri = 1, indexKeyA, indexKeyB; r < nbRowB; r++, ri++) {
                indexKeyB = hashTabB[r].Row;
                if (hashTabB[r].LinkedRow == 0) {
                    if (hashTabB[r].IsNotNull) {
                        cellsB.Match[indexKeyB] = XlRowMatch.NoMatch;
                        for (int ci = 1; ci <= nbColB; ci++)
                            cellsB.Diff[indexKeyB, ci] = true;
                    }
                } else if (cellsB.Match[indexKeyB] == XlRowMatch.Empty) {
                    indexKeyA = hashTabB[r].LinkedRow;
                    cellsB.Match[indexKeyB] = XlRowMatch.FullMatch;
                    for (int i = 0, indexColA, indexColB; i < nbValB; i++) {
                        indexColA = colValA[i];
                        indexColB = colValB[i];
                        if (ObjectsNotEquals(dataA[indexKeyA, indexColA], dataB[indexKeyB, indexColB])) {
                            cellsB.Diff[indexKeyB, indexColB] = true;
                            cellsB.Match[indexKeyB] = XlRowMatch.MatchWithDiff;
                        }
                    }
                }
            }

            return new RowSet[] { cellsA, cellsB };
        }

        public RowSet[] CompareRowsAligned(ref object[,] dataA, ref object[,] dataB, ref object[,] titlesA, ref object[,] titlesB, int[] colKeysA, int[] colKeysB, int[] colValA, int[] colValB, bool multi, XlAlign aligntype) {
            HashRow[] hashTabA = GetRowsKey(dataA, colKeysA);
            HashRow[] hashTabB = GetRowsKey(dataB, colKeysB);
            int nbRowA = hashTabA.Length;
            int nbRowB = hashTabB.Length;
            var listA = new List<Match>(nbRowA);
            var listB = new List<Match>();
            SortOnHash(ref hashTabA);
            SortOnHash(ref hashTabB);

            for (int r1 = 0, r2 = 0, nbMatch; r1 < nbRowA; r1++) {
                if (hashTabA[r1].IsNotNull) {
                    nbMatch = 0;
                    while (r2 < nbRowB) {
                        if (hashTabB[r2].IsNull)
                            r2++;
                        else if (hashTabA[r1] == hashTabB[r2]) {
                            listA.Add(new Match { IndexA = hashTabA[r1].Row, IndexB = hashTabB[r2].Row });
                            hashTabB[r2].LinkedRow = 1;
                            r2++;
                            nbMatch++;
                            if (multi == false)
                                break;
                        } else if (hashTabA[r1] < hashTabB[r2])
                            break;
                        else
                            r2++;
                    }
                    if (multi)
                        r2 -= nbMatch;
                    if (nbMatch == 0)
                        listA.Add(new Match { IndexA = hashTabA[r1].Row });
                }
            }

            foreach (HashRow hashrow in hashTabB)
                if (hashrow.LinkedRow == 0 && hashrow.IsNotNull)
                    listB.Add(new Match { IndexB = hashrow.Row });

            SortOnIndexA(ref listA);
            if (listB.Count != 0)
                SortOnIndexB(ref listB);

            RowSet[] result = null;
            switch (aligntype) {
                case XlAlign.Tables: result = Build_Aligned_Tables(ref dataA, ref dataB, ref titlesA, ref titlesB, ref listA, ref listB, colValA, colValB); break;
                case XlAlign.Columns: result = Build_Aligned_Columns(ref dataA, ref dataB, ref titlesA, ref titlesB, ref listA, ref listB, colValA, colValB); break;
                case XlAlign.Values: result = Build_Aligned_Values(ref dataA, ref dataB, ref titlesA, ref titlesB, ref listA, ref listB, colValA, colValB); break;
            }

            return result;
        }

        private RowSet[] Build_Aligned_Tables(ref object[,] dataA, ref object[,] dataB, ref object[,] titlesA, ref object[,] titlesB, ref List<Match> listA, ref List<Match> listB, int[] colValsA, int[] colVaslB) {
            int nbRowLeft = listA.Count, nbRowRigth = listB.Count + nbRowLeft;
            int nbColA = dataA.GetLength(1), nbColB = dataB.GetLength(1);
            int nbValA = colValsA.Length, nbValB = colVaslB.Length;
            int nbColMin = Math.Min(nbColA, nbColB);

            var cellsA = new RowSet(nbRowLeft, nbColA);
            cellsA.Titles = titlesA;
            var cellsB = new RowSet(nbRowRigth, nbColB);
            cellsB.Titles = titlesB;

            for (int r = 0, ri = 1; r < nbRowLeft; r++, ri++) {
                bool nolink = listA[r].IndexB == 0;
                for (int c = 0, ci = 1; c < nbColA; c++, ci++) {
                    cellsA.Data[ri, ci] = dataA[listA[r].IndexA, ci];
                    cellsA.Diff[ri, ci] = nolink;
                }
                if (nolink == false) {
                    cellsA.Match[ri] = cellsB.Match[ri] = XlRowMatch.FullMatch;
                    for (int ci = 1; ci <= nbColB; ci++)
                        cellsB.Data[ri, ci] = dataB[listA[r].IndexB, ci];
                    for (int i = 0; i < nbValA; i++) {
                        if (ObjectsNotEquals(dataA[listA[r].IndexA, colValsA[i]], dataB[listA[r].IndexB, colVaslB[i]])) {
                            cellsA.Diff[ri, colValsA[i]] = cellsB.Diff[ri, colVaslB[i]] = true;
                            cellsA.Match[ri] = cellsB.Match[ri] = XlRowMatch.MatchWithDiff;
                        }
                    }
                } else {
                    cellsA.Match[ri] = XlRowMatch.NoMatch;
                    //resB.Match[ri] = ERowMatch.NoMatch;
                }
            }

            for (int r = 0, ri = nbRowLeft + 1; ri <= nbRowRigth; r++, ri++) {
                cellsB.Match[ri] = XlRowMatch.NoMatch;
                for (int ci = 1; ci <= nbColB; ci++) {
                    cellsB.Data[ri, ci] = dataB[listB[r].IndexB, ci];
                    cellsB.Diff[ri, ci] = true;
                }
            }

            return new RowSet[] { cellsA, cellsB };
        }

        private RowSet[] Build_Aligned_Columns(ref object[,] dataA, ref object[,] dataB, ref object[,] titlesA, ref object[,] titlesB, ref List<Match> listA, ref List<Match> listB, int[] colValsA, int[] colValsB) {
            int nbRowA = listA.Count, nbRowB = listB.Count;
            int nbColA = dataA.GetLength(1), nbColB = dataB.GetLength(1);
            int nbRow = nbRowA + nbRowB;
            int nbCol = Math.Max(nbColA, nbColB) * 2;
            int nbValA = colValsA.Length, nbValB = colValsB.Length;
            int nbColMin = Math.Min(nbColA, nbColB);

            var cells = new RowSet(nbRow, nbCol);

            for (int r = 0, ri = 1; r < nbRowA; r++, ri++) {
                bool nolink = listA[r].IndexB == 0;
                for (int ci = 1, cc = 1; ci <= nbColA; ci++, cc += 2) {
                    cells.Data[ri, cc] = dataA[listA[r].IndexA, ci];
                    cells.Diff[ri, cc] = nolink;
                }

                if (nolink == false) {
                    cells.Match[ri] = cells.Match[ri] = XlRowMatch.FullMatch;
                    for (int ci = 1, cc = 2; ci <= nbColB; ci++, cc += 2)
                        cells.Data[ri, cc] = dataB[listA[r].IndexB, ci];
                    for (int i = 0; i < nbValA; i++) {
                        if (ObjectsNotEquals(dataA[listA[r].IndexA, colValsA[i]], dataB[listA[r].IndexB, colValsB[i]])) {
                            cells.Diff[ri, (colValsA[i] * 2) - 1] = cells.Diff[ri, colValsB[i] * 2] = true;
                            cells.Match[ri] = cells.Match[ri] = XlRowMatch.MatchWithDiff;
                        }
                    }
                } else {
                    cells.Match[ri] = XlRowMatch.NoMatch;
                }
            }

            for (int r = 0, ri = nbRowA + 1; ri <= nbRow; r++, ri++) {
                cells.Match[ri] = XlRowMatch.NoMatch;
                for (int ci = 1, cc = 2; ci <= nbColB; ci++, cc += 2) {
                    cells.Data[ri, cc] = dataB[listB[r].IndexB, ci];
                    cells.Diff[ri, cc] = true;
                }
            }

            for (int ci = 1, cc = 1; ci <= nbColA; ci++, cc += 2)
                cells.Titles[1, cc] = titlesA[1, ci];
            for (int ci = 1, cc = 2; ci <= nbColB; ci++, cc += 2)
                cells.Titles[1, cc] = titlesB[1, ci];

            return new RowSet[] { cells };
        }

        private RowSet[] Build_Aligned_Values(ref object[,] dataA, ref object[,] dataB, ref object[,] titlesA, ref object[,] titlesB, ref List<Match> listA, ref List<Match> listB, int[] colValsA, int[] colValsB) {
            int nbRowA = listA.Count, nbRowB = listB.Count;
            int nbColA = dataA.GetLength(1), nbColB = dataB.GetLength(1);
            int nbValA = colValsA.Length, nbValB = colValsB.Length;
            int nbColMin = Math.Min(nbColA, nbColB);
            int nbRow = nbRowA + nbRowB;
            int nbMaxCol = Math.Max(nbColA, nbColB);
            int nbMinCol = Math.Min(nbColA, nbColB);

            var cells = new RowSet(nbRow, nbMaxCol);

            for (int r = 0, ri = 1; r < nbRowA; r++, ri++) {
                if (listA[r].IndexB == 0) {
                    cells.Match[ri] = XlRowMatch.NoMatch;
                    for (int ci = 1; ci <= nbColA; ci++) {
                        cells.Data[ri, ci] = "[" + dataA[listA[r].IndexA, ci] + "][]";
                        cells.Diff[ri, ci] = true;
                    }
                } else {
                    cells.Match[ri] = XlRowMatch.FullMatch;
                    for (int ci = 1; ci <= nbMinCol; ci++)
                        cells.Data[ri, ci] = "[" + dataA[listA[r].IndexA, ci] + "][" + dataB[listA[r].IndexB, ci] + "]";
                    for (int ci = nbMinCol + 1; ci <= nbColA; ci++)
                        cells.Data[ri, ci] = "[" + dataA[listA[r].IndexA, ci] + "][]";
                    for (int ci = nbMinCol + 1; ci <= nbColB; ci++)
                        cells.Data[ri, ci] = "[][" + dataB[listA[r].IndexB, ci] + "]";

                    for (int i = 0; i < nbValA; i++) {
                        if (ObjectsNotEquals(dataA[listA[r].IndexA, colValsA[i]], dataB[listA[r].IndexB, colValsB[i]])) {
                            cells.Diff[ri, colValsB[i]] = true;
                            cells.Match[ri] = XlRowMatch.MatchWithDiff;
                        }
                    }
                }
            }

            for (int r = 0, ri = nbRowA + 1; ri <= nbRow; r++, ri++) {
                cells.Match[ri] = XlRowMatch.NoMatch;
                for (int ci = 1; ci <= nbColB; ci++) {
                    cells.Data[ri, ci] = "[][" + dataB[listB[r].IndexB, ci] + "]";
                    cells.Diff[ri, ci] = true;
                }
            }

            for (int ci = 1; ci <= nbMinCol; ci++)
                cells.Titles[1, ci] = "[" + titlesA[1, ci] + "][" + titlesB[1, ci] + "]";
            for (int ci = nbMinCol + 1; ci <= nbColA; ci++)
                cells.Titles[1, ci] = "[" + titlesA[1, ci] + "][]";
            for (int ci = nbMinCol + 1; ci <= nbColB; ci++)
                cells.Titles[1, ci] = "[][" + titlesB[1, ci] + "]";

            return new RowSet[] { cells };
        }

        HashRow[] GetRowsKey(object[,] data, int[] keyColIndexes) {
            int nbRow = data.GetLength(0);
            var hashKeys = new HashRow[nbRow];
            for (int r = 0, ri = 1; r < nbRow; r++, ri++) {
                string text = String.Empty;
                foreach (int colIndex in keyColIndexes)
                    text += GetObjectText(data[ri, colIndex]);
                hashKeys[r].Key = text;
                hashKeys[r].Hash = ComputeHash(text);
                hashKeys[r].Row = ri;
            }
            return hashKeys;
        }

        HashCell[] GetCellsKey(object[,] data) {
            int nbRow = data.GetLength(0);
            int nbCol = data.GetLength(1);
            int nbCells = nbRow * nbCol;
            var hashCell = new HashCell[nbCells];
            for (int ri = 1, cell = 0; ri <= nbRow; ri++) {
                string text = String.Empty;
                for (int ci = 1; ci <= nbCol; ci++) {
                    hashCell[cell].Row = ri;
                    hashCell[cell].Column = ci;
                    hashCell[cell].Key = GetObjectText(data[ri, ci]);
                    hashCell[cell].Hash = ComputeHash(hashCell[cell].Key);
                    cell++;
                }
            }
            return hashCell;
        }

        private string GetObjectText(object cell) {
            if (cell == null)
                return string.Empty;
            if (cell is string) {
                if (_ignorecase)
                    return ((string)cell).ToLower();
                return (string)cell;
            }
            return cell.ToString();
        }

        private bool ObjectsNotEquals(object a, object b) {
            if (a is string && b is string) {
                if (_ignorecase)
                    return string.CompareOrdinal(((string)a).ToLower(), ((string)b).ToLower()) != 0;
                return string.CompareOrdinal((string)a, (string)b) != 0;
            }
            return Equals(a, b) == false;
        }

    }
}
